#ifndef SRC_NODE_INTERNALS_H_
#define SRC_NODE_INTERNALS_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "node.h"
#include "util.h"
#include "util-inl.h"
#include "uv.h"
#include "v8.h"

#include <stdint.h>
#include <stdlib.h>

struct sockaddr;

// Variation on NODE_DEFINE_CONSTANT that sets a String value.
#define NODE_DEFINE_STRING_CONSTANT(target, name, constant)                                                            \
    do {                                                                                                               \
        v8::Isolate* isolate = target->GetIsolate();                                                                   \
        v8::Local<v8::String> constant_name = v8::String::NewFromUtf8(isolate, name);                                  \
        v8::Local<v8::String> constant_value = v8::String::NewFromUtf8(isolate, constant);                             \
        v8::PropertyAttribute constant_attributes = static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete); \
        target->DefineOwnProperty(isolate->GetCurrentContext(),                                                        \
                  constant_name,                                                                                       \
                  constant_value,                                                                                      \
                  constant_attributes)                                                                                 \
            .FromJust();                                                                                               \
    } while (0)

namespace node {

// Set in node.cc by ParseArgs with the value of --openssl-config.
// Used in node_crypto.cc when initializing OpenSSL.
extern const char* openssl_config;

// Set in node.cc by ParseArgs when --preserve-symlinks is used.
// Used in node_config.cc to set a constant on process.binding('config')
// that is used by lib/module.js
extern bool config_preserve_symlinks;

// Forward declaration
class Environment;

// If persistent.IsWeak() == false, then do not call persistent.Reset()
// while the returned Local<T> is still in scope, it will destroy the
// reference to the object.
template <class TypeName>
inline v8::Local<TypeName> PersistentToLocal(
    v8::Isolate* isolate,
    const v8::Persistent<TypeName>& persistent);

// Call with valid HandleScope and while inside Context scope.
v8::Local<v8::Value> MakeCallback(Environment* env,
    v8::Local<v8::Object> recv,
    const char* method,
    int argc = 0,
    v8::Local<v8::Value>* argv = nullptr);

// Call with valid HandleScope and while inside Context scope.
v8::Local<v8::Value> MakeCallback(Environment* env,
    v8::Local<v8::Object> recv,
    uint32_t index,
    int argc = 0,
    v8::Local<v8::Value>* argv = nullptr);

// Call with valid HandleScope and while inside Context scope.
v8::Local<v8::Value> MakeCallback(Environment* env,
    v8::Local<v8::Object> recv,
    v8::Local<v8::String> symbol,
    int argc = 0,
    v8::Local<v8::Value>* argv = nullptr);

// Call with valid HandleScope and while inside Context scope.
v8::Local<v8::Value> MakeCallback(Environment* env,
    v8::Local<v8::Value> recv,
    v8::Local<v8::Function> callback,
    int argc = 0,
    v8::Local<v8::Value>* argv = nullptr);

// Convert a struct sockaddr to a { address: '1.2.3.4', port: 1234 } JS object.
// Sets address and port properties on the info object and returns it.
// If |info| is omitted, a new object is returned.
v8::Local<v8::Object> AddressToJS(
    Environment* env,
    const sockaddr* addr,
    v8::Local<v8::Object> info = v8::Local<v8::Object>());

template <typename T, int (*F)(const typename T::HandleType*, sockaddr*, int*)>
void GetSockOrPeerName(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    T* const wrap = Unwrap<T>(args.Holder());
    if (wrap == nullptr)
        return args.GetReturnValue().Set(UV_EBADF);
    NODE_CHECK(args[0]->IsObject());
    sockaddr_storage storage;
    int addrlen = sizeof(storage);
    sockaddr* const addr = reinterpret_cast<sockaddr*>(&storage);
    const int err = F(&wrap->handle_, addr, &addrlen);
    if (err == 0)
        AddressToJS(wrap->env(), addr, args[0].As<v8::Object>());
    args.GetReturnValue().Set(err);
}

void SignalExit(int signo);
#ifdef __POSIX__
void RegisterSignalHandler(int signal,
    void (*handler)(int signal),
    bool reset_handler = false);
#endif

#ifndef arraysize
template <typename T, size_t N>
constexpr size_t arraysize(const T (&)[N])
{
    return N;
}
#endif

#ifndef ROUND_UP
#define ROUND_UP(a, b) ((a) % (b) ? ((a) + (b)) - ((a) % (b)) : (a))
#endif

#ifdef __GNUC__
#define MUST_USE_RESULT __attribute__((warn_unused_result))
#else
#define MUST_USE_RESULT
#endif

bool IsExceptionDecorated(Environment* env, v8::Local<v8::Value> er);

enum ErrorHandlingMode { FATAL_ERROR,
    CONTEXTIFY_ERROR };
void AppendExceptionLine(Environment* env,
    v8::Local<v8::Value> er,
    v8::Local<v8::Message> message,
    enum ErrorHandlingMode mode);

NO_RETURN void FatalError(const char* location, const char* message);

v8::Local<v8::Value> BuildStatsObject(Environment* env, const uv_stat_t* s);

enum Endianness {
    kLittleEndian, // _Not_ LITTLE_ENDIAN, clashes with endian.h.
    kBigEndian
};

inline enum Endianness GetEndianness()
{
    // Constant-folded by the compiler.
    const union {
        uint8_t u8[2];
        uint16_t u16;
    } u = {
        { 1, 0 }
    };
    return u.u16 == 1 ? kLittleEndian : kBigEndian;
}

inline bool IsLittleEndian()
{
    return GetEndianness() == kLittleEndian;
}

inline bool IsBigEndian()
{
    return GetEndianness() == kBigEndian;
}

// Like a `TryCatch` but exits the process if an exception was caught.
class FatalTryCatch : public v8::TryCatch {
public:
    FatalTryCatch(Environment* env);
    ~FatalTryCatch();

private:
    Environment* env_;
};

void ThrowError(v8::Isolate* isolate, const char* errmsg);
void ThrowTypeError(v8::Isolate* isolate, const char* errmsg);
void ThrowRangeError(v8::Isolate* isolate, const char* errmsg);
void ThrowErrnoException(v8::Isolate* isolate,
    int errorno,
    const char* syscall = nullptr,
    const char* message = nullptr,
    const char* path = nullptr);
void ThrowUVException(v8::Isolate* isolate,
    int errorno,
    const char* syscall = nullptr,
    const char* message = nullptr,
    const char* path = nullptr,
    const char* dest = nullptr);

class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
public:
    ArrayBufferAllocator()
        : env_(nullptr)
    {
    }

    inline void set_env(Environment* env) { env_ = env; }

    virtual void* Allocate(size_t size); // Defined in src/node.cc
    virtual void* AllocateUninitialized(size_t size)
    {
        return node::Malloc(size);
    }
    virtual void Free(void* data, size_t) { free(data); }

private:
    Environment* env_;
};

// Clear any domain and/or uncaughtException handlers to force the error's
// propagation and shutdown the process. Use this to force the process to exit
// by clearing all callbacks that could handle the error.
void ClearFatalExceptionHandlers(Environment* env);

enum NodeInstanceType { MAIN,
    WORKER,
    REMOTE_DEBUG_SERVER };

class NodeInstanceData {
public:
    NodeInstanceData(NodeInstanceType node_instance_type,
        uv_loop_t* event_loop,
        int argc,
        const char** argv,
        int exec_argc,
        const char** exec_argv,
        bool use_debug_agent_flag)
        : node_instance_type_(node_instance_type)
        , exit_code_(1)
        , event_loop_(event_loop)
        , argc_(argc)
        , argv_(argv)
        , exec_argc_(exec_argc)
        , exec_argv_(exec_argv)
        , use_debug_agent_flag_(use_debug_agent_flag)
    {
        NODE_CHECK_NE(event_loop_, nullptr);
    }

    uv_loop_t* event_loop() const
    {
        return event_loop_;
    }

    int exit_code()
    {
        NODE_CHECK(is_main());
        return exit_code_;
    }

    void set_exit_code(int exit_code)
    {
        NODE_CHECK(is_main());
        exit_code_ = exit_code;
    }

    bool is_main()
    {
        return node_instance_type_ == MAIN;
    }

    bool is_worker()
    {
        return node_instance_type_ == WORKER;
    }

    bool is_remote_debug_server()
    {
        return node_instance_type_ == REMOTE_DEBUG_SERVER;
    }

    int argc()
    {
        return argc_;
    }

    const char** argv()
    {
        return argv_;
    }

    int exec_argc()
    {
        return exec_argc_;
    }

    const char** exec_argv()
    {
        return exec_argv_;
    }

    bool use_debug_agent()
    {
        return is_main() && use_debug_agent_flag_;
    }

private:
    const NodeInstanceType node_instance_type_;
    int exit_code_;
    uv_loop_t* const event_loop_;
    const int argc_;
    const char** argv_;
    const int exec_argc_;
    const char** exec_argv_;
    const bool use_debug_agent_flag_;

    NODEJS_DISALLOW_COPY_AND_ASSIGN(NodeInstanceData);
};

namespace Buffer {
    v8::MaybeLocal<v8::Object> Copy(Environment* env, const char* data, size_t len);
    v8::MaybeLocal<v8::Object> New(Environment* env, size_t size);
    // Takes ownership of |data|.
    v8::MaybeLocal<v8::Object> New(Environment* env,
        char* data,
        size_t length,
        void (*callback)(char* data, void* hint),
        void* hint);
    // Takes ownership of |data|.  Must allocate |data| with malloc() or realloc()
    // because ArrayBufferAllocator::Free() deallocates it again with free().
    // Mixing operator new and free() is undefined behavior so don't do that.
    v8::MaybeLocal<v8::Object> New(Environment* env, char* data, size_t length);
} // namespace Buffer

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_INTERNALS_H_
